package cem.agony.ui.controller;

import java.util.HashMap;
import java.util.Map;

import cem.agony.model.SagStation;
import cem.agony.ui.RoadPoint;
import cem.agony.ui.StationPoint;

import javafx.beans.binding.Bindings;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;

public class MapBuilderController
{
    private static class DistanceBox extends HBox
    {
        private TextField distanceTextField = new TextField();
        private final int id;

        public DistanceBox( SagStation station )
        {
            setSpacing( 5 );
            this.id = station.getId();

            getChildren().addAll( new Label( "Distance to " + station.getName() ),
                                  distanceTextField,
                                  new Label( "miles" ) );
        }

        public void setDistance( String distance )
        {
            distanceTextField.setText( distance );
        }

        public String getDistance()
        {
            return distanceTextField.getText();
        }

        public int getStationId()
        {
            return id;
        }
    }

    @FXML
    Pane mapPane;
    @FXML
    ListView<StationPoint> stationListView;
    @FXML
    TextField nameTextField;
    @FXML
    VBox distancesVBox;
    @FXML
    Button removeSagButton;

    private Stage dialogStage;
    private boolean okayClicked = false;
    private final ObservableList<StationPoint> stations = FXCollections.observableArrayList();
    private final ObservableList<Node> distances = FXCollections.observableArrayList();
    private final Map<Integer, StationPoint> idToStation = new HashMap<Integer, StationPoint>();
    private int numAdded = 1;

    public MapBuilderController()
    {
    }

    public void setDialogStage( Stage dialogStage )
    {
        this.dialogStage = dialogStage;
    }

    @FXML
    private void initialize()
    {
        mapPane.setStyle( "-fx-border-color: #000000;" );
        Bindings.bindContent( distancesVBox.getChildren(), distances );

        stationListView.setItems( stations );
        stationListView.setCellFactory( new Callback<ListView<StationPoint>, ListCell<StationPoint>>()
        {
            @Override
            public ListCell<StationPoint> call( ListView<StationPoint> list )
            {
                return new StationPoint.StationCell();
            }
        } );
        stationListView.getSelectionModel().setSelectionMode( SelectionMode.SINGLE );
        stationListView.getSelectionModel()
            .selectedItemProperty()
            .addListener( new ChangeListener<StationPoint>()
            {
                @Override
                public void changed( ObservableValue<? extends StationPoint> observable,
                                     StationPoint oldValue, StationPoint newValue )
                {
                    setStationDetails( newValue );
                }
            } );

        setStationDetails( null );
    }

    @FXML
    private void handleAddStation()
    {
        SagStation newSag = new SagStation( "New Station " + numAdded++ );
        StationPoint station = new StationPoint( newSag );
        station.setTranslateX( Math.random() * ( mapPane.getWidth() - StationPoint.SIZE ) );
        station.setTranslateY( Math.random() * ( mapPane.getHeight() - StationPoint.SIZE ) );
        stations.add( station );
        mapPane.getChildren().add( station );
        idToStation.put( station.getStation().getId(), station );
    }

    @FXML
    private void handleRemoveStation()
    {
        StationPoint station = stationListView.getSelectionModel().getSelectedItem();
        if ( station != null )
        {
            stationListView.getSelectionModel().clearSelection(); // clear selection so it doesn't automatically select the previous index, which causes problems
            stations.remove( station );
            mapPane.getChildren().remove( station );
            idToStation.remove( station.getStation().getId() );

            for ( StationPoint node : stations )
            {
                RoadPoint road = node.removeStation( station.getStation().getId() );
                if ( road != null )
                {
                    mapPane.getChildren().remove( road );
                }
            }

            for ( RoadPoint road : station.getFromRoads() )
            {
                mapPane.getChildren().remove( road );
            }
        }
    }

    @FXML
    private void handleApplyChanges()
    {
        // TODO data validation first

        StationPoint station = stationListView.getSelectionModel().getSelectedItem();
        if ( station != null )
        {
            station.setName( nameTextField.getText() );

            for ( Node node : distances )
            {
                DistanceBox distance = (DistanceBox) node;

                StationPoint otherStation = idToStation.get( distance.getStationId() );
                if ( otherStation != null )
                {
                    double length = Double.parseDouble( distance.getDistance() );
                    if ( length > 0.0 )
                    {
                        RoadPoint road = station.addFromRoad( otherStation, length );
                        if ( road != null )
                        {
                            otherStation.addToRoad( station, road );
                            mapPane.getChildren().add( road );
                        }
                    }
                    else
                    {
                        station.removeStation( otherStation.getStation().getId() );
                        otherStation.removeStation( station.getStation().getId() );
                    }
                }
            }

            refreshStationListView();
        }
    }

    @FXML
    private void handleOkay()
    {
        okayClicked = true;
        dialogStage.close();
    }

    @FXML
    private void handleCancel()
    {
        okayClicked = false;
        dialogStage.close();
    }

    public boolean isOkayClicked()
    {
        return okayClicked;
    }

    private void setStationDetails( final StationPoint station )
    {
        distances.clear();

        if ( station == null )
        {
            removeSagButton.setDisable( true );
            nameTextField.setDisable( true );
            nameTextField.setText( "" );
        }
        else
        {
            removeSagButton.setDisable( false );
            nameTextField.setDisable( false );
            nameTextField.setText( station.getName() );

            for ( StationPoint node : stations )
            {
                if ( station.getStation().getId() != node.getStation().getId() )
                {
                    DistanceBox distance = new DistanceBox( node.getStation() );
                    distance.setDistance( Double.toString( station.getDistance( node.getStation()
                        .getId() ) ) );
                    distances.add( distance );
                }
            }
        }
    }

    // Work-around, since ListView doesn't have a manual refresh method
    private void refreshStationListView()
    {
        int selectedIndex = stationListView.getSelectionModel().getSelectedIndex();
        stationListView.setItems( null );
        stationListView.layout();
        stationListView.setItems( stations );
        stationListView.getSelectionModel().select( selectedIndex );
    }
}